﻿using System;
using System.Collections.Generic;
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using InvestmentIntelligence.DbModel.CustomModels;
using InvestmentIntelligence.DbModel.Models;

namespace InvestmentIntelligence.Data.Repository.EFImplementation
{
    using System.Data.Entity;

    public class TimeSerieRepository : IIPRepository<TimeSerie>, ITimeSeriesUpdatableRepository
    {
        public void AddMany(IEnumerable<TimeSerie> list)
        {
            BulkInsert(list, 0, SqlBulkCopyOptions.FireTriggers);
        }

        public List<T> GetList<T>(Func<IQueryable<TimeSerie>, IEnumerable<T>> f, int securityId, DateTime? startDate, DateTime? endDate)
        {
            return GetList(
                dc =>
                {
                    IQueryable<TimeSerie> list = null;
                    bool hasStartDate = startDate.HasValue;
                    bool hasEndDate = endDate.HasValue;
                    bool withAnyDate = hasStartDate || hasEndDate;
                    bool withBothDates = hasStartDate && hasEndDate;

                    if (!withAnyDate)
                    {
                        list = dc.AsNoTracking().Where(x => x.SecurityId == securityId);
                    }
                    else if (withBothDates)
                    {
                        list = dc.AsNoTracking().Where(x => x.SecurityId == securityId && x.Date >= startDate && x.Date <= endDate);
                    }
                    else if (hasStartDate)
                    {
                        list = dc.AsNoTracking().Where(x => x.SecurityId == securityId && x.Date >= startDate);
                    }
                    else
                    {
                        list = dc.AsNoTracking().Where(x => x.SecurityId == securityId && x.Date <= endDate);
                    }

                    return f(list);
                });
        }

        public List<int> GetDistinctSecurityIds()
        {
            return InternalGet(dc =>
            {
                var context = ((IObjectContextAdapter)dc).ObjectContext;
                context.CommandTimeout = 300;
                return dc.TimeSeries
                         .Select(c => c.SecurityId)
                         .Distinct();
            }, c => c.ToList());
        }

        public TimeSerie Get(int securityId, DateTime date)
        {
            return Get(dc =>
                       dc
                       .SingleOrDefault(x => x.SecurityId == securityId && x.Date == date.Date)) ?? new NullTimeSeries();
        }

        public List<SecurityStockReturn> GetSecurityStockReturns(IEnumerable<int> securityIds, DateTime minDate)
        {
            return GetList(dc => dc
                                       .Where(ts => securityIds.Contains(ts.SecurityId) && ts.Date >= minDate)
                                       .Where(ts => ts.StockReturn.HasValue)
                                       .Select(ts => new SecurityStockReturn
                                       {
                                           SecurityId = ts.SecurityId,
                                           Date = ts.Date,
                                           StockReturn = ts.StockReturn.Value
                                       }));
        }

        public IDictionary<int, DateTime> GetMaxDatePerSecurities(IEnumerable<int> items)
        {
            return InternalGet(dc =>
            {
                var context = ((IObjectContextAdapter)dc).ObjectContext;
                context.CommandTimeout = 0;
                return dc.TimeSeries
                    .Where(c => items.Contains(c.SecurityId))
                    .GroupBy(c => c.SecurityId)
                    .Select(c => new { SecurityId = c.Key, Date = c.Max(v => v.Date) });
            }, c => c.ToDictionary(k => k.SecurityId, v => v.Date));
        }


        public DateTime? GetLastDate(int key)
        {
            return Get(dc => dc.Where(c => c.SecurityId == key).Max(c => (DateTime?)c.Date));
        }





        public Dictionary<int, int> GetSecurityPricePointsPairs(IEnumerable<int> securityIds)
        {

            return
                Get(
                    dc => dc
                        .Where(x => securityIds.Contains(x.SecurityId))
                        .GroupBy(x => x.SecurityId)
                        .Select(result => new { result.Key, Value = result.Count() })

                        .ToDictionary(k => k.Key, val => val.Value));


        }

        public Dictionary<int, int> GetSecurityPricePointsPairs()
        {
            //return Get(
            //    dc => dc.GroupBy(x => x.SecurityId)
            //        .Select(result => new {result.Key, Value = result.Count()})
            //    .ToDictionary(k => k.Key, val => val.Value));

            return RunUnitOfWork(dc =>
            {
                var context = ((IObjectContextAdapter)dc).ObjectContext;
                context.CommandTimeout = 0;

                return (from s in dc.TimeSeries
                        group s by s.SecurityId
                            into gr
                            select new { gr.Key, Value = gr.Count() }).ToList();
            }).ToDictionary(k => k.Key, val => val.Value);
        }

        #region ITimeSerieRepository Members


        public Expression<Func<TimeSerie, bool>> GetQueryExpression(List<int> securityIds, DateTime? startDate, DateTime? endDate)
        {
            var timeSerie = Expression.Parameter(typeof(TimeSerie), "ts");
            var securityIdExp = Expression.Property(timeSerie, "SecurityId");
            var mi = typeof(List<int>).GetMethods().First(m => m.Name == "Contains" && m.GetParameters().Length == 1);
            var containsIdExp = Expression.Call(Expression.Constant(securityIds), mi, securityIdExp);

            Expression resultExp = containsIdExp;

            if (startDate.HasValue)
            {
                var timeSerieDateExp = Expression.Property(timeSerie, "Date");
                var startDateExp = Expression.Constant(startDate.Value);
                var moreThanStartDateExp = Expression.GreaterThanOrEqual(timeSerieDateExp, startDateExp);
                resultExp = Expression.AndAlso(resultExp, moreThanStartDateExp);
            }

            if (endDate.HasValue)
            {
                var timeSerieDateExp = Expression.Property(timeSerie, "Date");
                var endDateExp = Expression.Constant(endDate.Value);
                var lessThanEndDateExp = Expression.LessThanOrEqual(timeSerieDateExp, endDateExp);
                resultExp = Expression.AndAlso(resultExp, lessThanEndDateExp);
            }

            var queryExpression = Expression.Lambda<Func<TimeSerie, bool>>(resultExp, timeSerie);
            return queryExpression;
        }

        #endregion

      
    }

    public class NullTimeSeries : TimeSerie
    {
    }
}
